using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Xml.XPath;
using System.Xml.Serialization;
using System.Reflection;
using System.Diagnostics;

namespace A4G.Utils.Xml
{
	public class XmlValidationHelper
	{
		private ValidationEventHandler _validationEventHandler;

		public XmlValidationHelper(string xsdPath, string nameSpace)
			: this(new string[] { xsdPath }, new string[] {nameSpace})
		{
		}

		public XmlValidationHelper(string[] xsdPaths, string[] nameSpaces)
		{
			//ArgumentAsserter.AssertIsNotNull(xsdPaths, "xsdPaths");
			//ArgumentAsserter.AssertIsNotNull(nameSpaces, "nameSpaces");
			//ArgumentAsserter.AssertIsTrue(xsdPaths.Length > 0, "xsdPaths.Length > 0");
			//ArgumentAsserter.AssertAreEqual(xsdPaths.Length, nameSpaces.Length,
			//    "xsdPaths.Length", "nameSpace.Length");

			_validationEventHandler = new ValidationEventHandler(ValidationCallback);

			_schemaSet = new XmlSchemaSet();
			for (int i = 0; i < xsdPaths.Length; i++)
			{
				_schemaSet.Add(nameSpaces[i], xsdPaths[i]);
			}
		}

		public XmlValidationHelper(XmlSchemaSet schemaSet)
		{
			_validationEventHandler = new ValidationEventHandler(ValidationCallback);
			_schemaSet = schemaSet;
		}

		private readonly XmlSchemaSet _schemaSet;
		public XmlSchemaSet SchemaSet
		{
			get
			{
				return _schemaSet;
			}
		}

		private readonly List<Exception> _errors = new List<Exception>();
		private List<Exception> Errors
		{
			get
			{
				return _errors;
			}
		}

		public Exception[] GetErrors()
		{
			return Errors.ToArray();
		}

		private readonly List<Exception> _warnings = new List<Exception>();
		private List<Exception> Warnings
		{
			get
			{
				return _warnings;
			}
		}

		public Exception[] GetWarnings()
		{
			return Warnings.ToArray();
		}

		public void Validate(string xmlFile)
		{
			XmlReader xmlReader = new XmlTextReader(xmlFile);
			Validate(xmlReader);
			xmlReader.Close();
		}

		public void Validate(byte[] bytes)
		{
			MemoryStream xmlStream = new MemoryStream(bytes);
			try
			{
				Validate(xmlStream);
			}
			finally
			{
				xmlStream.Close();
			}
		}

		public void Validate(Stream xmlStream)
		{
			XmlReader xmlReader = new XmlTextReader(xmlStream);
			try
			{
				Validate(xmlReader);
			}
			finally
			{
				xmlReader.Close();
			}
		}

		public void Validate(XmlReader xmlReader)
		{
			Errors.Clear();
			Warnings.Clear();

			XmlReaderSettings settings = new XmlReaderSettings();
			settings.ValidationEventHandler += _validationEventHandler;
			settings.ValidationType = ValidationType.Schema;
			settings.Schemas = SchemaSet;

			XmlReader validatingReader = XmlReader.Create(xmlReader, settings);
			try
			{
				while (validatingReader.Read()) ;
			}
			catch (Exception e)
			{
				_errors.Add(e);
			}
			finally
			{
				validatingReader.Close();
			}

#if DEBUG
			Debug.WriteLine("Xml Validation Warnings:");
			if (Warnings.Count == 0)
			{
				Debug.WriteLine("- None");
			}

			foreach (Exception warning in Warnings)
			{
				XmlSchemaException xmlError = warning as XmlSchemaException;
				if (xmlError != null)
				{
					Debug.WriteLine(string.Format(
						"Line {0}, position {1}: {2}",
						xmlError.LineNumber,
						xmlError.LinePosition,
						xmlError.Message));
				}
				else
				{
					Debug.WriteLine(warning.ToString());
				}
			}

			Debug.WriteLine("Xml Validation Errors");
			if (Errors.Count == 0)
			{
				Debug.WriteLine("- None");
			}

			foreach (Exception error in Errors)
			{
				XmlSchemaException xmlError = error as XmlSchemaException;
				if (xmlError != null)
				{
					Debug.WriteLine(string.Format(
						"Line {0}, position {1}: {2}",
						xmlError.LineNumber,
						xmlError.LinePosition,
						xmlError.Message));
				}
				else
				{
					Debug.WriteLine(error.ToString());
				}
			}
#endif
		}

		private void ValidationCallback(object sender, ValidationEventArgs args)
		{
			switch (args.Severity)
			{
				case XmlSeverityType.Warning:
				{
					_warnings.Add(args.Exception);
					break;
				}
				case XmlSeverityType.Error:
				{
					_errors.Add(args.Exception);
					break;
				}
			}
		}
	}
}
